package com.google.protobuf.compiler.java;

import java.util.HashMap;
import java.util.Map;

import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.WireFormat;

/**
 * 属性生成器.
 */
public abstract class FieldGenerator {

	protected final FieldDescriptorProto descriptor;
	protected HashMap<String, String> _variables = new HashMap<>();

	public FieldGenerator(FieldDescriptorProto descriptor) {
		this.descriptor = descriptor;

		_variables.put("number", String.valueOf(descriptor.getNumber()));
		_variables.put("default_value", Util.getJavaDefaultValue(descriptor));
		_variables.put("type", Util.getASClass(descriptor));
		_variables.put("name", descriptor.getJsonName());// 属性名称
		_variables.put("capitalized_name", Util.makeCapitalizedName(descriptor.getJsonName()));// 首字母大写
		_variables.put("capitalized_type", Util.getCapitalizedType(descriptor));// 大定的类型
		_variables.put("tag", String.valueOf(Util.makeTag(descriptor)));
		_variables.put("is_default", Util.makeCompareDefaultValueExp(descriptor));
		_variables.put("is_not_default", Util.makeCompareNotDefaultValue(descriptor));
		_variables.put("field_type", Util.getASFieldType(descriptor));

		// string bytes int64属于标量，不能为null
		String type = Util.getASClass(descriptor);
		if ("String".equals(type) || "Int64".equals(type) || "ByteArray".equals(type)) {
			_variables.put("assign", "value || " + _variables.get("default_value"));
		} else {
			_variables.put("assign", "value");
		}
	}

	public abstract void generateAccessorCode(Printer printer);

	public abstract void generateWriteToCode(Printer printer);

	public abstract void generateReadFromCode(Printer printer);

	// 原始的属性代码生成
	public static class PrimitiveFieldGenerator extends FieldGenerator {
		private String trailingComments;// 右面注释

		public PrimitiveFieldGenerator(FieldDescriptorProto descriptor, String trailingComments) {
			super(descriptor);
			this.trailingComments = trailingComments;
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			// 没有注释...
			if (trailingComments.isEmpty()) {
				printer.writeln(_variables,
						"private #type# #name# = #default_value#;\n\n" + // 属性
								"public #type# get#capitalized_name#() {\n" + // GET方法
								"    return #name#;\n" + //
								"}\n\n" + //
								"public void set#capitalized_name#(#type# #name#) {\n" + // SET方法
								"    this.#name# = #name#;\n" + //
								"}");//
			}
			// 有注释...
			else {
				printer.writeln(_variables,
						"// " + trailingComments.trim() + "\n" + // 注释
								"private #type# #name# = #default_value#;\n\n" + // 属性
								"/**\n" + //
								" * 获取" + trailingComments.trim() + "\n" + //
								" *\n" + //
								" * @return " + trailingComments.trim() + "\n" + //
								" */\n" + //
								"public #type# get#capitalized_name#() {\n" + // GET方法
								"    return #name#;\n" + //
								"}\n\n" + //
								"/**\n" + //
								" * 设置" + trailingComments.trim() + "\n" + //
								" *\n" + //
								" * @param #name# " + trailingComments.trim() + "\n" + //
								" */\n" + //
								"public void set#capitalized_name#(#type# #name#) {\n" + // SET方法
								"    this.#name# = #name#;\n" + //
								"}");//
			}
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			// 只要这个属性不为默认值，那就写入output...
			printer.writeln(_variables,
					"if (#name##is_not_default#) {\n" + //
							"    output.write#capitalized_type#(#tag#, #name#);\n" + //
							"}");
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			printer.writeln(_variables,
					"case #tag#: {\n" + //
							"    this.#name# = input.read#capitalized_type#();\n" + //
							"    break;\n" + //
							"}");
		}
	}

	// List原生属性...
	public static class RepeatedPrimitiveFieldGenerator extends PrimitiveFieldGenerator {
		public RepeatedPrimitiveFieldGenerator(FieldDescriptorProto descriptor, String trailingComments) {
			super(descriptor, trailingComments);
			_variables.put("type", Util.getASClass1(descriptor));// 原生类型改为包装类.
			_variables.put("count", descriptor.getJsonName() + "Count");
			_variables.put("packed_tag", String.valueOf(descriptor.getNumber() << 3 | WireFormat.WIRETYPE_LENGTH_DELIMITED));

			_variables.put("packed", String.valueOf(Util.isTypePackable(descriptor)));
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			printer.writeln(_variables,
					"private List<#type#> #name# = new ArrayList<>();\n\n" + //
							"public List<#type#> get#capitalized_name#() {\n" + //
							"    return #name#;\n" + //
							"}\n" //
			);
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			// packed = true
			if (Util.isTypePackable(descriptor)) {
				printer.writeln(_variables,
						"" + "if (#name#.size() > 0) {\n" + //
								"    output.write#capitalized_type#List(#packed_tag#, #name#, #packed#);\n" + //
								"}");
			} else {
				printer.writeln(_variables,
						"" + "if (#name#.size() > 0) {\n" + //
								"    output.write#capitalized_type#List(#tag#, #name#, #packed#);\n" + //
								"}");
			}
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			// packed = true
			if (Util.isTypePackable(descriptor)) {
				printer.writeln(_variables,
						"case #packed_tag#: {\n" + //
								"    #name#.addAll(input.read#capitalized_type#List());\n" + //
								"    break;\n" + //
								"}");
			}
			// packed = false
			else {
				printer.writeln(_variables,
						"" + "case #tag#: {\n" + //
								"    #name#.add(input.read#capitalized_type#());\n" + //
								"    break;\n" + //
								"}");
			}
		}
	}

	public static class EnumFieldGenerator extends FieldGenerator {
		private final String trailingComments;

		public EnumFieldGenerator(FieldDescriptorProto descriptor, Map<String, String> classRef, String trailingComments) {
			super(descriptor);
			this.trailingComments = trailingComments;
			String name = classRef.get(descriptor.getTypeName());
			String fixName = name.substring(name.lastIndexOf(".") + 1);
			_variables.put("type", fixName);
			_variables.put("default_value", fixName + ".valueOf(0)");
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			if (trailingComments.isEmpty()) {
				printer.writeln(_variables,
						"private #type# #name# = #default_value#;\n" + //
								"public #type# get#capitalized_name#() {\n" + //
								"    return #name#;" + //
								"}\n" + //
								"public void set#capitalized_name#(#type# #name#) {\n" + //
								"    this.#name# = #name#;\n" + //
								"}");
			} else {
				printer.writeln(_variables,
						"// " + trailingComments.trim() + "\n" + // 注释
								"private #type# #name# = #default_value#;\n" + //
								"/**\n" + //
								" * 获取" + trailingComments.trim() + "\n" + //
								" *\n" + //
								" * @return " + trailingComments.trim() + "\n" + //
								" */\n" + //
								"public #type# get#capitalized_name#() {\n" + //
								"    return #name#;" + //
								"}\n" + //
								"/**\n" + //
								" * 设置" + trailingComments.trim() + "\n" + //
								" *\n" + //
								" * @param #name# " + trailingComments.trim() + "\n" + //
								" */\n" + //
								"public void set#capitalized_name#(#type# #name#) {\n" + //
								"    this.#name# = #name#;\n" + //
								"}");
			}
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			printer.writeln(_variables,
					"" + "if (#name##is_not_default#) {\n" + //
							"    output.writeInt32(#tag#, #name#.getValue());\n" + //
							"}");
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			printer.writeln(_variables,
					"" + "case #tag#: {\n" + //
							"    #name# = #type#.valueOf(input.readInt32());\n" + //
							"    break;\n" + //
							"}");
		}
	}

	public static class RepeatedEnumFieldGenerator extends EnumFieldGenerator {
		public RepeatedEnumFieldGenerator(FieldDescriptorProto descriptor, Map<String, String> classRef, String trailingComments) {
			super(descriptor, classRef, trailingComments);

			_variables.put("count", descriptor.getJsonName() + "Count");
			_variables.put("packed_tag", String.valueOf(descriptor.getNumber() << 3 | WireFormat.WIRETYPE_LENGTH_DELIMITED));
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			printer.writeln(_variables,
					"private List<#type#> #name# = new ArrayList<>();\n\n" + //
							"public List<#type#> get#capitalized_name#() {\n" + //
							"    return _#name#;\n" + //
							"}\n\n" //
			);
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			printer.writeln(_variables, "" + "if (_#name#.length > 0) {\n" + "    output.writePackedVector(_#name#, #number#, #field_type#);\n" + "}");
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			// packed = false
			printer.writeln(_variables, "" + "case #tag#: {\n" + "    _#name#.push(#type#.valueOf(input.readEnum()));\n" + "    break;\n" + "}");

			// packed = true
			printer.writeln(_variables,
					"" + "case #packed_tag#: {\n" + "    var #name#Values:Vector.<int> = new Vector.<int>();\n" + "    input.readPackedVector(#name#Values, #field_type#);\n" + "    _#name# = #type#.toEnums(#name#Values);\n" + "    break;\n" + "}");
		}
	}

	public static class MessageFieldGenerator extends FieldGenerator {
		public MessageFieldGenerator(FieldDescriptorProto descriptor, Map<String, String> classRef) {
			super(descriptor);
			String name = classRef.get(descriptor.getTypeName());
			_variables.put("type", name.substring(name.lastIndexOf(".") + 1));
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			printer.writeln(_variables,
					"private #type# #name# = #default_value#;\n" + //
							"public #type# get#capitalized_name#() {\n" + //
							"    return #name#;\n" + //
							"}\n" + //
							"public void set#capitalized_name#(#type# #name#) {\n" + //
							"    this.#name# = #name#;\n" + //
							"}");
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			printer.writeln(_variables,
					"if (#name##is_not_default#) {\n" + //
							"    output.writeMessage(#tag#, #name#);\n" + //
							"}");
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			printer.writeln(_variables,
					"case #tag#: {\n" + //
							"    #name# = new #type#();\n" + //
							"    input.readMessage(#name#);\n" + //
							"    break;\n" + //
							"}");
		}
	}

	public static class RepeatedMessageFieldGenerator extends MessageFieldGenerator {
		private final String trailingComments;

		public RepeatedMessageFieldGenerator(FieldDescriptorProto descriptor, Map<String, String> classRef, String trailingComments) {
			super(descriptor, classRef);
			this.trailingComments = trailingComments;
			_variables.put("packed", String.valueOf(Util.isTypePackable(descriptor)));
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			if (trailingComments.isEmpty()) {
				printer.writeln(_variables,
						"private List<#type#> #name# = new ArrayList<>();\n\n" + //
								"public List<#type#> get#capitalized_name#() {\n" + //
								"    return #name#;\n" + //
								"}\n" //
				);
			} else {
				printer.writeln(_variables,
						"// " + trailingComments.trim() + "\n" + // 注释
								"private List<#type#> #name# = new ArrayList<>();\n\n" + //
								"/**\n" + //
								" * 获取" + trailingComments.trim() + "\n" + //
								" *\n" + //
								" * @return " + trailingComments.trim() + "\n" + //
								" */\n" + //
								"public List<#type#> get#capitalized_name#() {\n" + //
								"    return #name#;\n" + //
								"}\n" //
				);
			}
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			printer.writeln(_variables,
					"if (#name#.size() > 0) {\n" + //
							"    output.writeMessageList(#tag#, #name#, #packed#);\n" + //
							"}");
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			printer.writeln(_variables,
					"case #tag#: {\n" + //
							"    #name#.add(input.readMessage(new #type#()));\n" + //
							"    break;\n" + //
							"}");
		}
	}

	public static class MapFieldGenerator extends FieldGenerator {
		public MapFieldGenerator(FieldDescriptorProto descriptor, DescriptorProto mapDescriptor, String scope, Map<String, String> classRef) {
			super(descriptor);

			FieldDescriptorProto.Type valueType = FieldDescriptorProto.Type.TYPE_BOOL;

			for (FieldDescriptorProto keyOrValue : mapDescriptor.getFieldList()) {
				if (keyOrValue.getNumber() == 1) {
					_variables.put("key_field_type", Util.getASFieldType(keyOrValue));
					_variables.put("key_type", Util.getASClass(keyOrValue));
					_variables.put("key_tag", String.valueOf(Util.makeTag(keyOrValue)));
				} else if (keyOrValue.getNumber() == 2) {
					_variables.put("value_field_type", Util.getASFieldType(keyOrValue));
					_variables.put("value_type", Util.getASClass(keyOrValue, classRef));
					_variables.put("value_tag", String.valueOf(Util.makeTag(keyOrValue)));
					valueType = keyOrValue.getType();
				}
			}

			_variables.put("map_class", Util.makeASClassName(scope, mapDescriptor.getName()));
			if (valueType == FieldDescriptorProto.Type.TYPE_MESSAGE) {
				_variables.put("new_type", "new " + _variables.get("value_type") + "()");
			} else {
				_variables.put("new_type", "null");
			}
		}

		@Override
		public void generateAccessorCode(Printer printer) {
			printer.writeln(_variables, "" + "private var _#name#:#map_class# = new #map_class#();\n" + "public function get #name#():#map_class# {\n" + "    return _#name#;\n" + "}");
		}

		@Override
		public void generateWriteToCode(Printer printer) {
			printer.writeln(_variables, "" + "output.writeMap(_#name#.toArray(), #number#, #key_field_type#,\n" + "        #value_field_type#);");
		}

		@Override
		public void generateReadFromCode(Printer printer) {
			printer.writeln(_variables, "" + "case #tag#: {\n" + "    var #name#Entry:MapEntry = input.readMap(#key_field_type#,\n" + "            #value_field_type#, #new_type#, #key_tag#, #value_tag#);\n"
					+ "    _#name#.putValue(#name#Entry.key, #name#Entry.value);\n" + "    break;\n" + "}");
		}
	}
}